package main

import (
	"fmt"
	"log"
	"net/http"

	"github.com/gin-gonic/gin"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
)

// 初始化数据库, 能过与数据库交互的 连接池对象: db
func setupDatabase() *gorm.DB {
	dsn := "root:123456@tcp(127.0.0.1:3306)/test?charset=utf8mb4&parseTime=True&loc=Local"
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
	if err != nil {
		panic("failed to connect database")
	}
	db.AutoMigrate(&Book{}) // 自动迁移
	return db.Debug()
}

func Failed(ctx *gin.Context, err error) {
	ctx.JSON(http.StatusBadRequest, gin.H{"code": 0, "msg": err.Error()})
}

// 规定好风格: JSON Restful Api
func main() {
	// gin Engine, 它包装了http server
	server := gin.Default()

	db := setupDatabase()
	// 配置业务路有, Book类型的资源的 一套简单的CRUD
	// 创建Book -> Book
	// POST, Body
	book := server.Group("/api/books")
	book.POST("", func(ctx *gin.Context) {
		// 获取Book用户传达的参数
		ins := new(Book)
		if err := ctx.ShouldBindJSON(ins); err != nil {
			Failed(ctx, err)
			return
		}

		// book, save
		if err := db.Save(ins).Error; err != nil {
			Failed(ctx, err)
			return
		}

		ctx.JSON(http.StatusOK, ins)
	})
	// 查询Book列表 -> []*Book
	// SELECT * FROM `books`
	book.GET("", func(ctx *gin.Context) {
		var books []Book
		if err := db.Find(&books).Error; err != nil {
			Failed(ctx, err)
			return
		}
		ctx.JSON(http.StatusOK, books)
	})
	// 查询Book详情
	book.GET("/:isbn", func(ctx *gin.Context) {
		var ins Book
		id := ctx.Param("isbn")
		if err := db.Where("isbn = ?", id).Take(&ins).Error; err != nil {
			Failed(ctx, fmt.Errorf("Book not found"))
			return
		}
		ctx.JSON(http.StatusOK, ins)
	})
	// 更新Book
	book.PUT("/:isbn", func(ctx *gin.Context) {
		id := ctx.Param("isbn")

		// 获取用户参数, 读取用户的更新参数
		req := BookSpec{}
		if err := ctx.ShouldBindJSON(&req); err != nil {
			Failed(ctx, err)
			return
		}

		// gorm更新是，如果字段为零值 就不更新该字段, is_sale 没办法更新为false
		// 如果越到零值，也需要更新，则需要转化为 指针类型
		if err := db.Where("isbn = ?", id).Model(&Book{}).Updates(req).Error; err != nil {
			Failed(ctx, err)
			return
		}

		// 针对有零值的字段独立更新
		// if err := db.Where("isbn = ?", id).Model(&Book{}).Update("is_sale", req.IsSale).Error; err != nil {
		// 	Failed(ctx, err)
		// 	return
		// }
		// // ......

		// 再次查询出来
		var ins Book
		if err := db.Where("isbn = ?", id).Take(&ins).Error; err != nil {
			Failed(ctx, fmt.Errorf("Book not found"))
			return
		}

		// 查询出更新后的数据
		ctx.JSON(http.StatusOK, ins)
	})
	// 删除Book
	book.DELETE("/:isbn", func(ctx *gin.Context) {
		id := ctx.Param("isbn")
		if err := db.Where("isbn = ?", id).Delete(&Book{}).Error; err != nil {
			Failed(ctx, err)
			return
		}
	})

	if err := server.Run("127.0.0.1:8080"); err != nil {
		log.Println(err)
	}
}

// Book 结构体定义
type Book struct {
	// grom:"column:isbn;", 具体文档: https://gorm.io/docs/models.html#Fields-Tags
	IsBN uint `json:"isbn" gorm:"primaryKey;column:isbn"`
	BookSpec
}

type BookSpec struct {
	Title  string  `json:"title"  gorm:"column:title;type:varchar(200)"`
	Author string  `json:"author"  gorm:"column:author;type:varchar(200);index"`
	Price  float64 `json:"price"  gorm:"column:price"`
	// bool false
	// nil 是零值, false
	IsSale *bool `json:"is_sale"  gorm:"column:is_sale"`
}

// 定义该对象映射到数据里 表的名称
func (t *Book) TableName() string {
	return "books"
}
